package mars.ns.core;
import scala.collection.mutable.Map
import mars.ns.HasState
import java.io.Serializable

/**
 * Class of a Node
 * part of a graph, connected with Links
 */
class Node extends Id with Equals with Serializable {

    // links are put into a set, since every link should be unique
    protected var links: Set[Link] = Set[Link]()

    //	getter of all links
    def getLinks: Set[Link] = links
    override def toString : String = this.id.toString
    
    /**
     * Equals method, two nodes are equal when having the same id
     */
    override def canEqual(other : Any) : Boolean = other match{
        case null => false
        case o : Node => this.id == o.id
        case _ => false
    }
    
    /**
     *  if can have the given link as a link for this node
     *	add link to other node obj from this node obj
     *	@return True if success
     */
    def addLink(link: Link): Boolean = {
        if (this.canHaveAsLink(link)) {
            this.links += link; return true
        }
        else {
            return false
        }
    }

    /**
     *  Can not add null as a link and
     *  All links must be unique
     *  Note: Can add link to itself
     */
    private def canHaveAsLink(link: Link): Boolean =
        link != null && !this.hasLink(link)

    /**
     * 	@effect  addLink(new Link(node,linktype))
     */
    def addLink(node: Node, linkType: LinkType) : Boolean = {
        return this.addLink(new Link(node, linkType))
    }
    /**
     * 	@effect  addLink(node, new DefaultLink)
     */
    def addLink(node: Node) : Boolean = {
        return this.addLink(node, new DefaultLink)
    }

    /**
     *	Add bidirectional link: this->other && other->this
     */
    def addDoubleLink(node: Node, linkType: LinkType) : Boolean = {
        if(node == null)
            return false
    	val isAdded = this.addLink(node, linkType)
    	return node.addLink(this, linkType) && isAdded
    }
    
    /**
     * 	@effect  addDoubleLink(node, new DefaultLink)
     */
    def addDoubleLink(node: Node) : Boolean = {
        return addDoubleLink(node, new DefaultLink)
    }

    /**
     * 	get all node objects that are linked with this node by any linkType
     *  
     *  @return	Set of nodes that are connected to this node
     */
    def getLinkedObjects(): Set[Node] = for (x <- this.links) yield x.getLinkedTo

    /**
     * 	get all node objects that are linked by a specific linktype
     * 	The isInstance method of the given linkType is used to compare LinkTypes
     * 	so SubClasses of the given linkType are accepted too
     *  Note: if linkType == null, getLinkedObjects() is called
     *  
     *  @param	linkType that this node connect with others
     *  @return	Set of nodes that are connected to this node by the given linkType
     */
    def getLinkedObjects(linkType: LinkType): Set[Node] = {
        if(linkType == null){
            return this.getLinkedObjects()            
        }
        else{
	        for {
	            x <- this.links
	            if linkType.getClass().isInstance(x.getType())
	        } yield x.getLinkedTo
        }
    }

    /**
     *	@return	A set of links to the given node from this node
     */
    def getLinksTo(nodeTo : Node) : Set[Link] = {
        for {
            x <- this.links
            if(x.getLinkedTo().canEqual(nodeTo))
        } yield x
    }
    
    /**
     * Removes a link from this node in one way
     */
    def removeLink(link: Link) : Boolean = {
        val len : Int = this.links.size
        this.links = this.links filterNot (link canEqual)
        return (this.links.size < len)
    }
    
    /**
     *	@effect removeLink(new Link(node,linkType))
     */
    def removeLink(node: Node, linkType: LinkType) : Boolean = {
        return this.removeLink(new Link(node, linkType))
    }

    /**
     * Removes the link in 2 directions (also removes if the link is only in one way)
     */
    def removeDoubleLink(link: Link) : Boolean = {
        return link.getLinkedTo.removeLink(new Link(this, link.getType)) && this.removeLink(link)
    }
    /**
     * 	@effect removeDoubleLink(new Link(node,linkType))
     */
    def removeDoubleLink(node: Node, linkType: LinkType) : Boolean = {
        return this.removeDoubleLink(new Link(node, linkType))
    }
    
    /**
     * Removes all links to a node, for all linkTypes
     */
    def removeLinksTo(node : Node){
    	this.links = this.links filterNot (link => link.getLinkedTo().canEqual(node))
    }
    
    /**
     * Removes all links from and to this node
     * After this method this node won't be linked to and does has an empty sets of links
     */
    def removeAllLinks(){
        this.getLinkedObjects() foreach (other => other.removeLinksTo(this))
        this.links = Set[Link]()
    }

    /**
     * 	@return True if there is a link x in links for which (link.canEqual(x))
     */
    def hasLink(link: Link): Boolean = this.links.exists(i => i.canEqual(link))

    /**
     * 	Get all connected nodes to the given node, splitted up in networks
     * 	foreach linkType, a network
     *  @param	The node from which all connected nodes have to be given
     *  @return List of networks
     */
    def getGroups: List[Network[Node]] = {
        var linkmap: Map[String, List[Node]] = Map[String, List[Node]]()
        this.links foreach {
            link => {
	           if (linkmap contains (link.getType.toString())) {
	                linkmap(link.getType.toString()) = (linkmap(link.getType.toString()) ::: List(link.getLinkedTo()))
	            }
	            else {
	                linkmap(link.getType.toString()) = List(link.getLinkedTo())
	            }
            }        
        }
        var groups = List[Network[Node]]();
        linkmap foreach { case (key, value) => (groups ::= new Network[Node](value)) }
        return groups;
    }
}

